﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Experimental.GraphView;
using UnityEngine.UIElements;
using UnityEngine;
using XLFrame.NodeEditor.Extensions;

namespace XLFrame.NodeEditor
{
    /// <summary>
    /// 节点的Port管理类
    /// </summary>
    public class NodeViewPort
    {
        /// <summary>
        /// 此节点创建的输入端口
        /// </summary>
        public readonly Dictionary<string, Port> InputPort = new Dictionary<string, Port>();

        /// <summary>
        /// 此节点创建的输出端口
        /// </summary>
        public readonly Dictionary<string, Port> OutputPort = new Dictionary<string, Port>();

        /// <summary>
        /// 输入端口名称和GUID
        /// </summary>
        private Dictionary<string,string> inputPortNameGUID = new Dictionary<string, string>();

        /// <summary>
        /// 输出端口名称和GUID
        /// </summary>
        private Dictionary<string,string> outputPortNameGUID = new Dictionary<string, string>();

        private readonly NodeViewBase nodeView;

        /// <summary>
        /// 不显示节点的名称类型,在初始化节点前设置
        /// </summary>
        public bool NoShowPortNameType { get; set; } = false;
        
        
        /// <summary>
        /// 当端口数据发生改变时调用,在输入输出端口的事件之后
        /// </summary>
        public event Action<PortDataEvent> OnPortDataChangeEvent;
        
        /// <summary>
        /// 当输入端口数据发生改变时调用
        /// </summary>
        public event Action<PortDataEvent> OnInputPortDataChangeEvent;
        
        /// <summary>
        /// 当输出端口数据发生改变时调用
        /// </summary>
        public event Action<PortDataEvent> OnOutputPortDataChangeEvent;
        
        /// <summary>
        /// 当创建了端口时,包含加载存档的加载和初始化的创建以及动态的创建
        /// </summary>
        public event Action<Port> OnPortCreateEvent;

        /// <summary>
        /// 当运行时创建了新端口
        /// </summary>
        public event Action<Port> OnRuntimeCreatePortEvent;
        
        /// <summary>
        /// 当端口运行被移除时调用
        /// </summary>
        public event Action<Port> OnRuntimePortRemoveEvent;
        

        public NodeViewPort(NodeViewBase nodeView)
        {
            this.nodeView = nodeView;
            inputContainer = nodeView.inputContainer;
            outputContainer = nodeView.outputContainer;
            
            // 注册事件
            OnPortCreateEvent += OnPortCreate;
        }

        private VisualElement outputContainer { get; set; }
        private VisualElement inputContainer { get;set; }

        /// <summary>
        /// 获取输入数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public T GetInputData<T>(string name)
        {
            var data = GetInputPort(name);
            if (data is null)
            {
                Debug.LogError($"获取输入数据时:{name}端口为null");
                return default;
            }
            
            var portData = data.GetPortData();
            try
            {
                if(portData.Value is PortData portData2)
                {
                    return portData2.Value.Conversion<T>();
                }
                else
                {
                    Debug.LogError($"无法将输入数据'{name}:{portData.Value}'的值转换为类型'{typeof(T).Name}'。");
                    return default;
                }
            }
            catch (InvalidCastException)
            {
                Debug.LogError($"无法将输入数据'{name}'的值转换为类型'{typeof(T).Name}'。");
                return default;
            }
        }

        /// <summary>
        /// 获取返回数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public T GetOutputData<T>(string name)
        {
            var data = GetOutputPort(name);
            if (data is null)
            {
                Debug.LogError($"获取输出数据时:{name}端口为null");
                return default;
            }

            return data.GetPortData().Value.Conversion<T>();
        }

        /// <summary>
        /// 设置返回端口的数据
        /// </summary>
        /// <param name="name"></param>
        /// <param name="obj"></param>
        public void SetOutputData(string name,object obj)
        {
            var data = GetOutputPort(name);
            if (data is null)
            {
                Debug.LogError($"设置输出数据时:{name}端口为null");
                return;
            }
            data.GetPortData().Value = obj;
        }

        /// <summary>
        /// 获取输入端口数据 将输出端数据引用放到当前的输入端,然后返回输入集合
        /// </summary>
        /// <returns></returns>
        public List<PortData> GetInputPortData()
        {
            List<PortData> ret = new List<PortData>();
            foreach (var item in InputPort.Values)
            {
                var thisNode = item.userData as PortData;
                ret.Add(thisNode);
            }
            return ret;
        }

        /// <summary>
        /// 获取输出端口数据
        /// </summary>
        /// <returns></returns>
        public List<PortData> GetOutputPortData()
        {
            List<PortData> ret = new List<PortData>();
            foreach (var item in OutputPort.Values)
            {
                ret.Add(item.userData as PortData);
            }
            return ret;
        }
        
        /// <summary>
        /// [注册自OnPortCreateEvent]
        /// 当端口被创建时调用 
        /// </summary>
        /// <param name="obj"></param>
        private void OnPortCreate(Port obj)
        {
            // 判断是否在存档中,如果在,则表示为加载存档时的调用
            // 如果不在则表示新增的端口,有可能时运行时动态添加的端口,或者第一次创建节点时的初始化创建
            // 需要区分上述情况,拿到动态创建的端口事件
            
            // 需要从NodeData拿数据,因为其他数据可能未初始化完成
            var inArchive = false;
            
            inArchive = obj.direction == Direction.Input ? 
                nodeView.NodeData.PortInInputPortDatas(obj.name) :
                nodeView.NodeData.PortInOutputPortDatas(obj.name);
    
            // 不在存档中,并且不是第一次创建节点,必定是运行时动态添加的端口
            if (!inArchive && nodeView.IsNewNode)
            {
                // 通知运行时新增端口事件
                OnRuntimeCreatePortEvent?.Invoke(obj);
            }
        }
        
        /// <summary>
        /// [创建端口时注册自 PortData.OnPropertyChangeEvent]
        /// 当输入节点的值发生改变时 
        /// 在创建端口时注册事件,以保证了所有存在于此节点的端口都会调用此方法
        /// 这个事件只通知输出端口,由此处通过输出端口找到连接的输入端口,然后将值传递过去
        /// </summary>
        /// <param name="portData"></param>
        void OutputPortDataOnPropertyChange(PortDataEvent portData)
        {
            OnOutputPortDataChangeEvent?.Invoke(portData);
            OnPortDataChangeEvent?.Invoke(portData);
        }
        /// <summary>
        /// [创建端口时注册自 PortData.OnPropertyChangeEvent]
        /// 当输入端的值被改变时调用 
        /// </summary>
        /// <param name="portData"></param>
        void InputPortDataOnPropertyChange(PortDataEvent portData)
        {
            OnInputPortDataChangeEvent?.Invoke(portData);
            OnPortDataChangeEvent?.Invoke(portData);
        }
        
        /// <summary>
        /// 连接两个端口并保存数据
        /// </summary>
        /// <param name="outputPort"></param>
        /// <param name="inputPort"></param>
        public EdgeLine LinkNodes(Port outputPort, Port inputPort)
        {
            if (outputPort is null || inputPort is null) return null;
            if (outputPort == inputPort) return null;

            // 单连接端口，输入端仅支持一个端口连接，断开之前的连接并保存数据
            if(inputPort.capacity == Port.Capacity.Single)
            {
                inputPort.connections.ToList().ForEach(x => nodeView.NodeGraphView.RemoveEdge((EdgeLine)x));
            }
            if(outputPort.capacity == Port.Capacity.Single)
            {
                outputPort.connections.ToList().ForEach(x => nodeView.NodeGraphView.RemoveEdge((EdgeLine)x));
            }

            var edge = new EdgeLine
            {
                output = outputPort,
                input = inputPort
            };

            edge.input.Connect(edge);
            edge.output.Connect(edge);

            nodeView.NodeGraphView.AddEdge(edge);
            
            return edge;
        }

        /// <summary>
        /// 添加输入端口默认节点 用于连接和设置位置, 兼容InputNode 需要在OnAddView中调用或者更晚
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="portName"></param>
        /// <returns></returns>
        public T AddInputPortDefNode<T>(string portName) where T : NodeViewBase, new()
        {
            var inputPort = GetInputPort(portName);
            if (inputPort.connections.Any())
            {
                // 已存在连接
                return null;
            }

            var node = nodeView.NodeGraphView.CreateNode<T>();
            LinkNodes(node.NodePort.GetOutputPort("输出"), inputPort);

            nodeView.ScheduleDelayedAction(() =>
            {
                float w = node.inputContainer.layout.width + 40;
                float h = (node.layout.height - node.mainContainer.layout.height) / 2;
                var minPos = inputPort.ChangeCoordinatesTo(nodeView.parent, new Vector2(-w, -h));
                node.SetPosition(minPos);
                nodeView.NodeGraphView.AddToSelection(node);
            });

            return node;
        }

        /// <summary>
        /// 通过guid或者name获取输入端口
        /// </summary>
        /// <param name="guidOrName"></param>
        /// <returns></returns>
        public Port GetInputPort(string guidOrName)
        {
            if (InputPort.TryGetValue(guidOrName, out var port))
            {
                return port;
            }
            if(inputPortNameGUID.TryGetValue(guidOrName,out var guid))
            {
                return InputPort[guid];
            }
            foreach (var item in InputPort.Values)
            {
                string nameType = ToolExtensions.RemoveNameType(item.portName);
                if (nameType == guidOrName)
                {
                    inputPortNameGUID[nameType] = item.name;
                    return item;
                }
            }
            return null;
        }

        /// <summary>
        /// 通过guid或者name获取输出端口
        /// </summary>
        /// <param name="guidOrName"></param>
        /// <returns></returns>
        public Port GetOutputPort(string guidOrName)
        {
            if (OutputPort.TryGetValue(guidOrName, out var port))
            {
                return port;
            }
            if (outputPortNameGUID.TryGetValue(guidOrName, out var guid))
            {
                return OutputPort[guid];
            }
            foreach (var item in OutputPort.Values)
            {
                string nameType = ToolExtensions.RemoveNameType(item.portName);
                if (nameType == guidOrName)
                {
                    outputPortNameGUID[nameType] = item.name;
                    return item;
                }
            }
            return null;
        }

        /// <summary>
        /// 获取输出端口连接的节点
        /// </summary>
        /// <param name="guidOrName"></param>
        /// <returns></returns>
        public List<NodeViewBase> GetOutputPortLinkNode(string guidOrName)
        {
            var port = GetOutputPort(guidOrName);
            if (port is null) return null;
            return port.connections.Select(x => x.input.node as NodeViewBase).ToList();
        }
        
        /// <summary>
        /// 获取输入端口连接的节点
        /// </summary>
        /// <param name="guidOrName"></param>
        /// <returns></returns>
        public List<NodeViewBase> GetInputPortLinkNode(string guidOrName)
        {
            var port = GetInputPort(guidOrName);
            if (port is null) return null;
            return port.connections.Select(x => x.output.node as NodeViewBase).ToList();
            // TODO 添加一个集线器模块,检测到集线器自动忽略集线器并找到集线器连接的节点
        }
        
        //-----------移除端口相关----------------
        
        /// <summary>
        /// 移除端口并移除数据以及通知事件
        /// </summary>
        /// <param name="guid"></param>
        /// <param name="isInput"></param>
        /// <returns></returns>
        public bool RemovePort(string guid,bool isInput)
        {
            var isRemove = isInput ? InputPort.Remove(guid, out var port) : 
                OutputPort.Remove(guid, out port);
            if (!isRemove) return false;
            
            string nameType = ToolExtensions.RemoveNameType(port.portName);
            var isRemoveName = isInput ? inputPortNameGUID.Remove(nameType) : 
                outputPortNameGUID.Remove(nameType);
            
            nodeView.NodeGraphView.DataUpdate();

            // 移除连接
            foreach (var item in port.connections.ToList())
            {
                nodeView.NodeGraphView.RemoveEdge((EdgeLine)item);
            }
            
            port.parent.Remove(port);
            
            nodeView.NodeData.RemovePort(guid);
            
            OnRuntimePortRemoveEvent?.Invoke(port);

            
            return true;
        }
        
        
        
        //-----------创建端口相关----------------
        // 除了输入输出端,还要增加一个其他端,用于用户自定义端口位置,这样反序列化才知道如何排列端口
        // TODO 目前暂不支持自定义端口位置,反序列化加载时,没有位置信息

        /// <summary>
        /// 创建一个端口
        /// </summary>
        /// <param name="portDirection"></param>
        /// <param name="capacity"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        protected Port CreatePortForNode(Direction portDirection, Port.Capacity capacity, Type type)
        {
            return nodeView.InstantiatePort(Orientation.Horizontal, portDirection, capacity, type);
        }

        /// <summary>
        /// 创建一个端口,保存数据,所有如果此方法创建的端口均已存在UserData = PortData类
        /// </summary>
        /// <param name="type">端口支持的类型</param>
        /// <param name="portName">端口显示的名称</param>
        /// <param name="PortGuid">端口GUID</param>
        /// <param name="isInput">是否为输入端口</param>
        /// <param name="capacity">支持的连接数量</param>
        /// <param name="saveData">是否保存</param>
        /// <param name="addContainer">是否加入到显示视图</param>
        /// <param name="CID">端口加入的容器ID Node为节点容器</param>
        /// <returns></returns>
        public Port CreatePortForNode(Type type, string portName, string PortGuid, bool isInput,
                                      Port.Capacity capacity = Port.Capacity.Single,bool saveData = true,
                                      bool addContainer=true,string CID="Node")
        {
            // 处理端口名称对ExecutionLine特判
            var _portName = saveData? NoShowPortNameType? portName : $"{portName}({type.Name})" : portName;
            if (type == typeof(ExecutionLine))
            {
                capacity = Port.Capacity.Single;
                if (string.IsNullOrEmpty(portName))
                {
                    _portName = isInput ? "入口" : "执行";
                }
                else
                    _portName = portName;
            }
            // 判断是否重名,如果重名直接抛出异常
            if (inputPortNameGUID.ContainsKey(_portName)|| outputPortNameGUID.ContainsKey(_portName))
            {
                throw new Exception($"创建端口时:{_portName}端口名称重复");
            }
            

            var port = CreatePortForNode(isInput ? Direction.Input : Direction.Output, capacity, type);
            port.name = PortGuid;
            port.portName = _portName;
            port.portColor = PortColor.Instance.GetColor(type);
            port.userData = new PortData(port.portName, port.name, type.GetTypeNameAndAssembly(),nodeView.NodeData.Attributes);
            
            // 监听值变化事件
            if (port.direction == Direction.Output)
            {
                port.GetPortData().OnPropertyChangeEvent += OutputPortDataOnPropertyChange;
            }
            else
            {
                port.GetPortData().OnPropertyChangeEvent += InputPortDataOnPropertyChange;
            }
            
            // 显示节点并保存数据和相关引用
            if (isInput)
            {
                if(addContainer)
                    inputContainer.Add(port);
                InputPort.Add(PortGuid, port);
            }
            else
            {
                if(addContainer)
                    outputContainer.Add(port);
                OutputPort.Add(PortGuid, port);
            }
            
            // 仅在编辑视图下会动态添加端口,保存后的解释层不会动态添加端口,所以解释层并不会添加端口
            // 通知端口创建事件
            OnPortCreateEvent?.Invoke(port);

            if (saveData)
            {
                var addEnd = isInput
                    ? nodeView.NodeData.AddInputPort(new PortViewData(port,CID))
                    : nodeView.NodeData.AddOutputPort(new PortOutputViewData(port,CID));
            }
            
            return port;
        }
        
        /// <summary>
        /// 创建输入执行线端口
        /// </summary>
        /// <returns></returns>
        public Port CreateInputExecutionLinePort(string cname = "入口")
        {
            return CreateInputPort<ExecutionLine>(cname);
        }

        /// <summary>
        /// 创建输出执行线端口
        /// </summary>
        /// <returns></returns>
        public Port CreateOutputExecutionLinePort(string cname = "执行")
        {
            return CreateOutputPort<ExecutionLine>(cname);
        }

        /// <summary>
        /// 创建一个输入端口,端口名称和显示名为portName
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="portName"></param>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public Port CreateInputPort<T>(string portName, Port.Capacity capacity = Port.Capacity.Single)
        {
            return CreateInputPort(typeof(T), portName, capacity);
        }

        /// <summary>
        /// 创建一个输出端口,端口名称和显示名为portName
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="portName"></param>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public Port CreateOutputPort<T>(string portName, Port.Capacity capacity = Port.Capacity.Multi)
        {
            return CreateOutputPort(typeof(T), portName, capacity);
        }

        /// <summary>
        /// 创建一个输入端口,端口名称和显示名为portName
        /// </summary>
        /// <param name="type"></param>
        /// <param name="portName"></param>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public Port CreateInputPort(Type type, string portName, Port.Capacity capacity = Port.Capacity.Single)
        {
           return CreateInputPort(type, portName, Guid.NewGuid().ToString(), capacity);
        }

        /// <summary>
        /// 创建一个输出端口,端口名称和显示名为portName
        /// </summary>
        /// <param name="type"></param>
        /// <param name="portName"></param>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public Port CreateOutputPort(Type type, string portName, Port.Capacity capacity = Port.Capacity.Multi)
        {
            return CreateOutputPort(type, portName, Guid.NewGuid().ToString(), capacity);
        }

        /// <summary>
        /// 创建一个输入端口
        /// </summary>
        /// <param name="type"></param>
        /// <param name="portName"></param>
        /// <param name="portGuid"></param>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public Port CreateInputPort(Type type,string portName,string portGuid, Port.Capacity capacity= Port.Capacity.Single)
        {
           var port = CreatePortForNode(type, portName, portGuid, true, capacity);
            return port;
        }

        /// <summary>
        /// 创建一个输出端口
        /// </summary>
        /// <param name="type"></param>
        /// <param name="portName"></param>
        /// <param name="portGuid"></param>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public Port CreateOutputPort(Type type, string portName, string portGuid, Port.Capacity capacity = Port.Capacity.Multi)
        {
            var port = CreatePortForNode(type, portName, portGuid, false, capacity);
            return port;
        }
    }
}